from abc import ABC, abstractmethod
import collections
import sys
from concurrent.futures import ThreadPoolExecutor
import json
import time
import tkinter as tk
from tkinter import messagebox
from ....utils.types import *
from ....utils.loggers import ic_debugger, record_logger, dev_logger, prod_logger
from ....utils.utils import executor_pool_exception_callback
from ....zuds.zuds import Zuds_Tester_Base
from ..notebook_tabs.can_manualTab_Frame import Can_ManualTab_Frame
from ....zlgcan.zlgcan import ZCAN_Transmit_Data, ZCAN_TransmitFD_Data
from ....globals import GLOBAL_ZCANLIB


class FunctestBase_Can(ABC, tk.Toplevel):
    """
    基础抽象类
    """
    _objDict = {}

    def __new__(cls, PosId, *args, **kwargs):
        '''
        同一个PosId只能有一个实例，避免窗口多开出现问题
        '''
        PosId = PosId
        if PosId not in cls._objDict:
            newObj = object.__new__(cls)
            return newObj
        else:
            cls._objDict[PosId].resetTestState()
            cls._objDict[PosId].deiconify()
            # 重新生成是还是要返回对象，否则就是None
            return cls._objDict[PosId]

    def resetTestState(self):
        # 将上一次测试的展示状态,重置
        for key, data in enumerate(self.testDatas):
            self.sendDataLabelDict[key].configure(
                text='sendstate', background='gray')
            self.recvDataLabelDict[key].configure(
                text='recvstate', background='gray')
            self.alltestResMsg.config(foreground='gray', text='批量测试结果')

    def __init__(
        self, masterUI, parentWin: Can_ManualTab_Frame, PosId="xy", name="XXXX模块demo", MsgId=0xFFF, rcevId=0x999, testDatas=None, **kwargs
    ) -> None:
        # 如果之前已经实例化过，就不在进行初始化。
        # 否则就进行后面的初始化，并且在最后把该示例放到示例的字典_objDict中
        if PosId in self._objDict:
            return
        else:
            self._objDict[PosId] = self
        """
        root 是主窗口的实例self
        """
        super().__init__()
        self.zcanlib = GLOBAL_ZCANLIB
        self.device_handle = Zuds_Tester_Base.device_handle
        # 给父窗口设定个醒目的变量名
        self.parentWin = parentWin
        self.masterUI = masterUI
        self.title(f"测试控制面板----{name}-{PosId}")

        self.name = name
        self.PosId = PosId
        # 设定id值的随时跟踪变化
        self.MsgId = MsgId
        self.idIntVar = tk.IntVar()
        self.idIntVar.set(self.MsgId)
        # 设定反馈报文的id号
        self.rcevId = rcevId
        self.idRcevIntvar = tk.IntVar()
        self.idRcevIntvar.set(self.rcevId)

        # 设定只有一个所有线程共用的线程的线程池，避免多线程交叉执行导致的数据异常
        # 每个单元测试下面的每个key代表的功能测试所用的线程池
        self.testSinglePoolUnitKey = ThreadPoolExecutor(
            max_workers=1, thread_name_prefix="FunctestBase_testSinglePoolUnitKey")
        # 单元测试allTest所用的线程池
        self.testSinglePoolUnit = ThreadPoolExecutor(
            max_workers=1, thread_name_prefix="FunctestBase_testSinglePoolUnit")

        # 创建一个测试对象过滤id的deque，后续直接可以查看里面的报文，是否有预期反馈的
        from ....utils.msg_queue import global_can_unitTestRecvQueues
        key_symbol = id(self)
        self.recvMsgQueue = global_can_unitTestRecvQueues.create_queue(
            key_symbol=key_symbol, name=f'功能测试{self.PosId}', filter_ids=[self.rcevId])

        if testDatas is not None:
            self.testDatas = testDatas
        else:
            self.testDatas = [
                UnitTestData_Type(name="name1", send="senddata1",
                                  rcev="recvdata1"),
                UnitTestData_Type(name="name2", send="senddata2",
                                  rcev="recvdata2"),
                UnitTestData_Type(name="name3", send="senddata3",
                                  rcev="recvdata3"),
                UnitTestData_Type(name="name4", send="senddata4",
                                  rcev="recvdata4"),
            ]

        # 前面设定完了基础值,然后再做gui的初始化
        self.viewInit()
        self.protocol('WM_DELETE_WINDOW', self.closeHandle)

    def closeHandle(self):
        # 把关闭窗口的destroy改为withdraw,避免关闭窗口后的在开启报错.
        # tkinter.messagebox.showwarning('关闭窗口', '您将关闭窗口self...')
        self.withdraw()

    @abstractmethod
    def viewInit(self, *args, **kwargs):
        pass

    @abstractmethod
    def sendControlMsg(self, *args, **kwargs):
        pass


##############################################################

    def manual_sendMsg_can_std(self, can_id, data_string='1122334455667788', msg_len=8):
        '''
        '''

        def wrapper(try_counts=3):
            msg = ZCAN_Transmit_Data()
            '''
            有两个成员
                frame
                transmit_type
            发送方式，0=正常发送，1=单次发送，2=自发自收，3=单次自发自收。
            发送方式说明如下：
            正常发送：在ID仲裁丢失或发送出现错误时，CAN控制器会自动重发，直到发送成功，或发送超时，或总线关闭。
            单次发送：在一些应用中，允许部分数据丢失，但不能出现传输延迟时，自动重发就没有意义了。在这些应用中，一般会以固定的时间间隔发送数据，自动重发会导致后面的数据无法发送，出现传输延迟。使用单次发送，仲裁丢失或发送错误，CAN控制器不会重发报文。
            自发自收：产生一次带自接收特性的正常发送，在发送完成后，可以从接收缓冲区中读到已发送的报文。
            单次自发自收：产生一次带自接收特性的单次发送，在发送出错或仲裁丢失不会执行重发。在发送完成后，可以从接收缓冲区中读到已发送的报文。
            '''
            msg.transmit_type = 0

            # 设置id
            msg.frame.can_id = can_id

            # 设置远程帧1，数据帧0
            msg.frame.rtr = 0

            # 设置标准帧0， 扩展帧1
            msg.frame.eff = 0

            # 设置消息长度
            msg.frame.can_dlc = msg_len

            def split_string(string, n):
                '''
                    每隔n单位，分割字符串
                '''
                return [string[i:i+n] for i in range(0, len(string), n)]

            data_list = split_string(data_string, 2)
            for i in range(msg_len):
                if i < len(data_list):
                    try:
                        msg.frame.data[i] = int(data_list[i], 16)
                    except:
                        msg.frame.data[i] = 0
                else:
                    msg.frame.data[i] = 0

            # 发送数据的结构体数组首指针
            send_num = 1
            send_msgs = (ZCAN_Transmit_Data * send_num)()
            send_msgs[0] = msg
            ret = self.zcanlib.Transmit(
                self.device_handle, send_msgs, send_num)
            if ret != send_num:
                # 尝试3次
                if try_counts > 0:
                    wrapper(try_counts - 1)
                else:
                    messagebox.showwarning(
                        '发送报文', f'发送{can_id}::{data_string}失败了')
                    print(f'[file:{__file__}]=>[line:{(sys._getframe().f_lineno)}]==>发送{
                        can_id}::{data_string}失败了')
                    return 'error'
            return ret

        # 如果出错会继续尝试3次
        return wrapper()

    def manual_sendMsg_canFD_std(self, can_id, data_string='1122334455667788', msg_len=8):
        '''
            [ ]: 函数待测试确认
        '''

        wrapper()

        def wrapper(try_counts=3):
            msg = ZCAN_TransmitFD_Data()

            '''
            有两个成员
                frame
                transmit_type
            发送方式，0=正常发送，1=单次发送，2=自发自收，3=单次自发自收。
            发送方式说明如下：
            正常发送：在ID仲裁丢失或发送出现错误时，CAN控制器会自动重发，直到发送成功，或发送超时，或总线关闭。
            单次发送：在一些应用中，允许部分数据丢失，但不能出现传输延迟时，自动重发就没有意义了。在这些应用中，一般会以固定的时间间隔发送数据，自动重发会导致后面的数据无法发送，出现传输延迟。使用单次发送，仲裁丢失或发送错误，CAN控制器不会重发报文。
            自发自收：产生一次带自接收特性的正常发送，在发送完成后，可以从接收缓冲区中读到已发送的报文。
            单次自发自收：产生一次带自接收特性的单次发送，在发送出错或仲裁丢失不会执行重发。在发送完成后，可以从接收缓冲区中读到已发送的报文。
            '''
            msg.transmit_type = 0

            # 设置id
            msg.frame.can_id = can_id

            # 设置远程帧1，数据帧0
            msg.frame.rtr = 0

            # 设置标准帧0， 扩展帧1
            msg.frame.eff = 0

            # 设置长度
            msg.frame.can_dlc = msg_len

            def split_string(string, n):
                '''
                    每隔n单位，分割字符串
                '''
                return [string[i:i+n] for i in range(0, len(string), n)]

            data_list = split_string(data_string, 2)
            for i in range(msg_len):
                if i < len(data_list):
                    try:
                        msg.frame.data[i] = int(data_list[1], 16)
                    except:
                        msg.frame.data[i] = 0
                else:
                    msg.frame.data[i] = 0

            # 发送数据的结构体数组首指针
            send_num = 1
            send_msgs = (ZCAN_TransmitFD_Data * send_num)()
            send_msgs[0] = msg
            ret = self.zcanlib.TransmitFD(
                self.device_handle, send_msgs, send_num)
            if ret != send_num:
                if try_counts > 0:
                    wrapper(try_counts - 1)
                else:
                    messagebox.showwarning(
                        '发送报文', f'发送{can_id}::{data_string}失败了')
                    print(f'[file:{__file__}]=>[line:{(sys._getframe().f_lineno)}]==>发送{
                        can_id}::{data_string}失败了')
                    return 'error'
            return ret


class FunctestTemplate_Can(FunctestBase_Can):

    def viewInit(self):
        self.opFrame = tk.Frame(self, borderwidth=2, relief="raise")
        self.opFrame.grid(row=1, column=1)

        if 1:
            self.opFrameSub1 = tk.Frame(
                self.opFrame, borderwidth=1, relief="groove")
            self.opFrameSub1.grid(row=1, column=0)
            self.alltestBtn = tk.Button(
                self.opFrameSub1,
                text="批量测试",
                # TODO: 字体更改
                # font=("Arail", 16),
                command=self.allTest,
            )
            self.alltestBtn.grid(row=0, column=0)
            self.alltestResMsg = tk.Message(
                self.opFrameSub1, width=200, foreground='gray', text='批量测试结果')
            self.alltestResMsg.grid(row=0, column=1)

        # 单元测试frame
        if 1:
            self.opFrameSub2 = tk.Frame(
                self.opFrame, borderwidth=1, relief="groove")
            self.opFrameSub2.grid(row=2, column=0)

            self.sendDataEntryDict = {}
            self.sendDataStringDict = {}
            self.sendDataBtnDict = {}
            self.sendDataLabelDict = {}
            self.recvDataLabelDict = {}
            self.rcevDataStringDict = {}
            self.rcevDataEntryDict = {}

            # 用一个代用key的字典,通过for循环,把每个测试数据与设定的控件关联起来
            self.testDataDict = {}
            for key, data in enumerate(self.testDatas):
                # 发送数据设定
                print(f'[file:{__file__}]=>[line:{(sys._getframe().f_lineno)}]==>key:{
                    key}, data:{data}')
                self.testDataDict[key] = data
                self.sendDataStringDict[key] = tk.StringVar()
                self.sendDataStringDict[key].set(data['send'])
                # self.sendDataStringDict[key].set(data.send)
                self.sendDataEntryDict[key] = tk.Entry(
                    self.opFrameSub2,
                    width=20,
                    state="disabled",
                    textvariable=self.sendDataStringDict[key],
                )
                self.sendDataEntryDict[key].grid(
                    row=key, column=0, pady=5, padx=20)
                # self.sendDataEntryDict[key].bind(
                #     "<KeyRelease>", self.validateMsgData)

                self.sendDataBtnDict[key] = tk.Button(
                    self.opFrameSub2,
                    # text=data.name,
                    text=data['name'],
                    command=self.sendControlMsg(key),
                )
                self.sendDataBtnDict[key].grid(
                    row=key, column=1, pady=5, padx=20)

                self.sendDataLabelDict[key] = tk.Label(
                    self.opFrameSub2, text="sendstate")
                self.sendDataLabelDict[key].grid(
                    row=key, column=2, pady=5, padx=20)

                self.recvDataLabelDict[key] = tk.Label(
                    self.opFrameSub2, text="rcevstate")
                self.recvDataLabelDict[key].grid(
                    row=key, column=3, pady=5, padx=20)

                self.rcevDataStringDict[key] = tk.StringVar()
                # self.rcevDataStringDict[key].set(data.rcev)
                self.rcevDataStringDict[key].set(data['rcev'])
                self.rcevDataEntryDict[key] = tk.Entry(
                    self.opFrameSub2,
                    width=20,
                    state="disabled",
                    textvariable=self.rcevDataStringDict[key],
                )
                self.rcevDataEntryDict[key].grid(
                    row=key, column=4, pady=5, padx=20)
                # self.rcevDataEntryDict[key].bind(
                #     "<KeyRelease>", self.validateMsgData)

        if 1:
            self.configFrame = tk.Frame(self, borderwidth=2, relief="raise")
            self.configFrame.grid(row=4, column=1)
            self.enableEntrysBtn = tk.Button(
                self.configFrame, text="输入框开启", command=self.enableEntrysBtnCmd
            )
            self.enableEntrysBtn.pack(side="left")
            self.disableEntrysBtn = tk.Button(
                self.configFrame, text="输入框屏蔽", command=self.disableEntrysBtnCmd
            )
            self.disableEntrysBtn.pack(side="left")
            self.configBtn = tk.Button(
                self.configFrame, text="设置测试全局参数", command=self.initConfigPanel
            ).pack(side="left")

    def enableEntrysBtnCmd(self):
        for _, entry in self.sendDataEntryDict.items():
            entry.configure(state="normal")
        for _, entry in self.rcevDataEntryDict.items():
            entry.configure(state="normal")

    def disableEntrysBtnCmd(self):
        for _, entry in self.sendDataEntryDict.items():
            entry.configure(state="disabled")
        for _, entry in self.rcevDataEntryDict.items():
            entry.configure(state="disabled")

    def initConfigPanel(self):
        self.configPanel = tk.Toplevel(self)
        self.configPanel.title(f"{self.name}测试参数设置")
        self.configPanelFr1 = tk.Frame(self.configPanel)
        self.configPanelFr1.grid(row=1, column=1)
        tk.Label(self.configPanelFr1, text="预留该页面用于设定通用数据").grid(
            row=1, columnspan=2, column=1
        )

        self.configPanelIdLabel = tk.Label(
            self.configPanelFr1, text="模块MsgId设置")
        self.configPanelIdLabel.grid(row=2, column=1)
        self.idEntry = tk.Entry(self.configPanelFr1,
                                textvariable=self.idIntVar)
        self.idEntry.grid(row=2, column=2)

        self.configPanelRcevIdLabel = tk.Label(
            self.configPanelFr1, text="反馈报文Id设置")
        self.configPanelRcevIdLabel.grid(row=3, column=1)
        self.idEntry1 = tk.Entry(
            self.configPanelFr1, textvariable=self.idRcevIntvar)
        self.idEntry1.grid(row=3, column=2)

        self.configPanelFr2 = tk.Frame(self.configPanel)
        self.configPanelFr2.grid(row=2, column=1)
        self.configPanelFr2InfoTipsLabel = tk.Label(
            self.configPanelFr2, text="备份说明信息")
        self.configPanelFr2InfoTipsLabel.grid(row=1, column=1, columnspan=2)
        self.configPanelFr2InfoTipsText = tk.Text(
            self.configPanelFr2, width=50, height=10)
        self.configPanelFr2InfoTipsText.grid(row=2, column=1, columnspan=2)

        self.configPanelFr2infoOperatorName = tk.Label(
            self.configPanelFr2, text="备份人姓名:")
        self.configPanelFr2infoOperatorName.grid(
            row=3, column=1)
        self.configPanelFr2Operator = tk.StringVar()
        self.configPanelFr2Operator.set("xxxxxx")
        self.configPanelFr2OperatorEntry = tk.Entry(
            self.configPanelFr2, textvariable=self.configPanelFr2Operator
        )
        self.configPanelFr2OperatorEntry.grid(row=3, column=2)
        self.configPanelFr2BackupBtn = tk.Button(
            self.configPanelFr2, text="备份配置数据", command=lambda: self.backupConfig()
        )
        self.configPanelFr2BackupBtn.grid(row=4, column=1, columnspan=2)
        pass

    def backupConfig(self):
        askyesno = tk.messagebox.askyesno("确认", "确认要备份配置吗?")
        if askyesno is False:
            return
        nowConfigs = self.parentWin.canFD_unitTestmodelDict

        # 当前的模块设定数据
        curConfig = nowConfigs[self.PosId]
        ic_debugger(curConfig)
        # dev_logger.debug(curConfig)
        # 发送报文id
        curConfig.MsgId = self.idIntVar.get()
        # 接收反馈报文id
        curConfig.rcevId = self.idRcevIntvar.get()

        tempTestDatas = []
        # 通过key索引把原来改过的内容更新回去到设置里.
        for key, value in self.testDataDict.items():
            unittest = UnitTestData_Type(
                name=value['name'],
                # name=value.name,
                send=self.sendDataStringDict[key].get(),
                rcev=self.rcevDataStringDict[key].get(),
            )
            tempTestDatas.append(unittest)
        curConfig.testDatas = tempTestDatas

        ic_debugger(curConfig)

        # 更新当前测试数据
        nowConfigs[self.PosId] = curConfig

        # 把pydantic类型转换为字典，后面才可以json.dump
        configList = []
        for testDT in nowConfigs.values():
            dictData = testDT.model_dump()
            # dictData = testDT.toDict()
            configList.append(dictData)

        # 设定data数据
        self.parentWin.configJson["data"] = configList

        # 设定info数据
        self.parentWin.configJson["info"]["operator"] = self.configPanelFr2Operator.get(
        )
        self.parentWin.configJson["info"]["Tips"] = self.configPanelFr2InfoTipsText.get(
            "1.0", "end"
        )

        with open("config/can_backupConfig.json", mode="w", encoding="utf-8") as f:
            json.dump(self.parentWin.configJson, f, ensure_ascii=False)

        pass

    def sendControlMsg(self, key):

        def wrapper():
            try_counts = 3

            def _sendMsg(try_counts=3):

                strData = self.testDatas[key]['send']

                can_id = self.MsgId
                data_string = strData
                # dev_logger.debug(f"待发送的信息内容为:{data}")
                record_logger.debug(f"待发送的信息内容为:{data_string}")

                # 创建一个该测试的唯一的queue
                # 把接收缓存清理一下,否则可能运行时recvMsgDeque里面收到很多原先缓存中的数据
                self.zcanlib.ClearBuffer(self.device_handle)

                # 发送消息前情况消息队列,后面就只查看队列中有没有预期的返回信息即可
                self.recvMsgQueue.clear()
                # 打开后台接收信息event开关
                # [ ] 待完善多个测试共用以及完善合适地方关闭
                self.masterUI.can_readMsg_thread_open()

                # [ ] 待确认此处是否有try处理？
                if True:
                    # 发送消息
                    res = self.manual_sendMsg_can_std(
                        can_id=can_id, data_string=data_string)
                    if res == "error":
                        self.sendDataLabelDict[key].configure(
                            text="发送NG", background="red")
                        record_logger.debug(f'测试信号未发送成功')
                        prod_logger.error('未成功发送')
                        ret_string = "未成功发送"
                    # 发送成功返回发送的数据条数
                    elif res >= 1:
                        self.sendDataLabelDict[key].configure(
                            text="发送OK", background="green")
                        # 正确发送后, 从总线读取响应信息
                        # 读取之前已经把信息栈清空,然后再经过一段时间判断,收到的信息里有没有预期的数据
                        recvResult = _checkRecvMsg()
                        if recvResult is True:
                            self.recvDataLabelDict[key].configure(
                                text="返回OK", background="green"
                            )
                            record_logger.debug('数据校验正确')
                            ret_string = "数据校验正确"
                        elif try_counts > 0:
                            try_counts -= 1
                            # 此处要关闭一次，否则会导致多次打开，关闭次数不够，一直在后台打开
                            self.masterUI.can_readMsg_thread_close()
                            return _sendMsg(try_counts)
                        else:
                            self.recvDataLabelDict[key].configure(
                                text="返回NG", background="red")
                            prod_logger.error('数据校验失败')
                            record_logger.debug('数据校验失败')
                            ret_string = '数据校验失败'
                    else:
                        prod_logger.error('发送数据有误')
                        record_logger.debug('发送数据有误')
                        ret_string = '发送数据有误'

                    print(f'[file:{__file__}]=>[line:{
                        (sys._getframe().f_lineno)}]==>{ret_string}')

                    self.masterUI.can_readMsg_thread_close()
                    return ret_string

            def _checkRecvMsg():

                # 读取预期的返回数据11XXXXXXXXXX
                hopeResult = self.testDatas[key]['rcev']
                record_logger.debug(f'预期返回的信号是：{hopeResult}')

                # FIXME: 根据实际情况设定等待信息反馈的时间,当前的线程和接收帧数据的线程不是同一个，
                # 所以当前线程休眠时，接收数据的线程继续工作，如果sleep时间过长，就会导致队列中数据太多
                # 如果sleep时间太短，队列中又可能没有收到数据
                time.sleep(1)
                # 此处把队列中的数据全部取出来，前面发送之前已经清空过一次，这时候取出来就是这个时段收到的数据
                recvMsgs = list(self.recvMsgQueue.deque)
                msgCount = len(recvMsgs)
                record_logger.debug(f'发送信号后，回收到{msgCount}条符合id滤波的数据')
                if msgCount < 1:
                    record_logger.debug('未收到id符合的数据')
                    return False

                # 只有收到数据才会做for循环遍历
                for msg in recvMsgs:
                    # print(msg)
                    if self.rcevId != msg.can_id:
                        record_logger.debug(str(msg) + '----id不匹配')
                        continue
                    else:

                        # 将msg转化为结构化的CanMsg_std_type
                        # ic_debugger(msg_data)
                        msg_data: CanMsg_std_type = self.masterUI.CANMsg2View_std(
                            msg)
                        msg_data_str = msg_data.data_str
                        record_logger.debug(f'收到的信息是：{msg_data_str}')
                        dataLength = len(hopeResult)
                        for i in range(dataLength):
                            # 设置值不是X，同时也不相等，就跳出去继续下一个
                            if hopeResult[i] != 'X' and hopeResult[i] != msg_data_str[i]:
                                # TODO: 此处的i值是不能马上显示的,最后才统一显示,所以实际上最后只显示了最后一条i值
                                record_logger.debug(
                                    str(msg.data) + f'<<第{i}条msg--不匹配--hope>>' + str(hopeResult))
                                break
                        # 全部都能匹配上，返回True
                        else:
                            record_logger.debug(
                                str(msg.data) + f'<<msg--匹配--hope>>' + str(hopeResult))
                            return True

                # 全部都遍历完了，还没有符合条件的，返回False
                else:
                    record_logger.debug('未回收到预期的反馈信号')
                    return False

            # 因为在外面用result来获取返回值，所以此处不需要对异常进行add_done_callback进行捕获了
            return self.testSinglePoolUnitKey.submit(_sendMsg)
        return wrapper

    def allTest(self):
        """
        左前座椅(主驾驶)
        此处用多线程,避免一个报错,把其他的都给堵塞死了
        """

        dev_logger.debug("run callback allTest....")

        def _allTest():
            dev_logger.debug(f"run _allTest....{self.testDatas}")

            # sendControlMsg 接收测试信息索引号为参数
            res = collections.defaultdict(int)
            for key, _ in enumerate(self.testDatas):
                td = self.sendControlMsg(key)()
                # td 是一个异步线程池任务的future对象，td.result()就是其任务执行后反馈的结果
                # 可能反馈的结果就是在闭包包裹的_sendMsg函数中return的内容,有两种情况，数据校验正确 或 数据校验失败
                # res 是一个字典，key是td.sendControlMsg的返回值,value是计数值
                tdRet = td.result()
                # dev_logger.debug(str(tdRet))
                res[tdRet] += 1
            # res是个字典，示例如下：{"数据校验正确":5, "数据校验错误":3}
            dev_logger.debug(res)
            resString = ''
            # 把测试结果用字典形式列出来
            resFlag = True
            for key, value in res.items():
                # res 是一个字典，key是td.sendControlMsg的返回值,value是计数值
                resString += f'{key}:{value}\n'
                # 只要有一个错误，结果就是错误的，需要确认
                if key != '数据校验正确':
                    resFlag = False
            # 去掉最后的一个空行
            resString = resString[:-1]
            self.alltestResMsg.configure(text=resString)
            foreground = 'green' if resFlag else 'red'
            self.alltestResMsg.configure(foreground=foreground)

            dev_logger.debug('alltest 运行结束....')
            # [ ]:后续考虑优化返回值的结构

            result = AlltestReturnType(resFlag, resString)

            return result

        # 把发送和接受的工作放到一个单独的线程里执行,避免堵塞主线程,每个的发送和接收仍然是同步的,避免self.ecan的使用冲突
        # td = threading.Thread(target=_allTest)
        # td.start()
        # 此处还是要把res return出去，否则在unittestCommand("xy")时，不能正常运行
        res = self.testSinglePoolUnit.submit(_allTest)
        res.add_done_callback(executor_pool_exception_callback)
        return res

        # 把结果返回出去，在外面就可以通过result()接收到，内部函数return的返回值
        # future = self.testSinglePoolUnit.submit(_allTest)
        # 虽然通过future.result()获取到内部函数的返回值，如果内部有报警，会抛出来
        # 此处不能用future.result()，因为result方法是阻塞的，会导致后面无法再继续执行了。
        # future.result()
        # return future


class FunctestTemplate_Can_Batc(FunctestTemplate_Can):
    '''
        batc 的座椅按摩检测也是发送的普通can信号，通过智能网关转发
    '''

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

    def backupConfig(self):
        askyesno = tk.messagebox.askyesno("确认", "确认要备份配置吗?")
        if askyesno is False:
            return
        nowConfigs = self.parentWin.canFD_unitTestmodelDict

        # 当前的模块设定数据
        curConfig = nowConfigs[self.PosId]
        ic_debugger(curConfig)
        # dev_logger.debug(curConfig)
        # 发送报文id
        curConfig.MsgId = self.idIntVar.get()
        # 接收反馈报文id
        curConfig.rcevId = self.idRcevIntvar.get()

        tempTestDatas = []
        # 通过key索引把原来改过的内容更新回去到设置里.
        for key, value in self.testDataDict.items():
            unittest = UnitTestData_Type(
                name=value['name'],
                # name=value.name,
                send=self.sendDataStringDict[key].get(),
                rcev=self.rcevDataStringDict[key].get(),
            )
            tempTestDatas.append(unittest)
        curConfig.testDatas = tempTestDatas

        ic_debugger(curConfig)

        # 更新当前测试数据
        nowConfigs[self.PosId] = curConfig

        # 把pydantic类型转换为字典，后面才可以json.dump
        configList = []
        for testDT in nowConfigs.values():
            dictData = testDT.model_dump()
            # dictData = testDT.toDict()
            configList.append(dictData)

        # 设定data数据
        self.parentWin.configJson["data"] = configList

        # 设定info数据
        self.parentWin.configJson["info"]["operator"] = self.configPanelFr2Operator.get(
        )
        self.parentWin.configJson["info"]["Tips"] = self.configPanelFr2InfoTipsText.get(
            "1.0", "end"
        )

        with open("config/batc_seat_backupConfig.json", mode="w", encoding="utf-8") as f:
            json.dump(self.parentWin.configJson, f, ensure_ascii=False)

        pass

    def sendControlMsg(self, key):

        def wrapper():
            try_counts = 3

            def _sendMsg(try_counts=3):

                strData = self.testDatas[key]['send']

                can_id = self.MsgId
                data_string = strData
                # dev_logger.debug(f"待发送的信息内容为:{data}")
                record_logger.debug(f"待发送的信息内容为:{data_string}")

                # 创建一个该测试的唯一的queue
                # 把接收缓存清理一下,否则可能运行时recvMsgDeque里面收到很多原先缓存中的数据
                self.zcanlib.ClearBuffer(self.device_handle)

                # 发送消息前情况消息队列,后面就只查看队列中有没有预期的返回信息即可
                self.recvMsgQueue.clear()
                # 打开后台接收信息event开关
                # [ ] 待完善多个测试共用以及完善合适地方关闭
                self.masterUI.can_readMsg_thread_open()

                # [ ] 待确认此处是否有try处理？
                if True:
                    # 发送消息
                    res = self.manual_sendMsg_can_std(
                        can_id=can_id, data_string=data_string)
                    if res == "error":
                        self.sendDataLabelDict[key].configure(
                            text="发送NG", background="red")
                        record_logger.debug(f'测试信号未发送成功')
                        prod_logger.error('未成功发送')
                        ret_string = "未成功发送"
                    # 发送成功返回发送的数据条数
                    elif res >= 1:
                        self.sendDataLabelDict[key].configure(
                            text="发送OK", background="green")
                        # 正确发送后, 从总线读取响应信息
                        # 读取之前已经把信息栈清空,然后再经过一段时间判断,收到的信息里有没有预期的数据
                        recvResult = _checkRecvMsg()
                        if recvResult is True:
                            self.recvDataLabelDict[key].configure(
                                text="返回OK", background="green"
                            )
                            record_logger.debug('数据校验正确')
                            ret_string = "数据校验正确"
                        elif try_counts > 0:
                            try_counts -= 1
                            # 此处要关闭一次，否则会导致多次打开，关闭次数不够，一直在后台打开
                            self.masterUI.can_readMsg_thread_close()
                            return _sendMsg(try_counts)
                        else:
                            self.recvDataLabelDict[key].configure(
                                text="返回NG", background="red")
                            prod_logger.error('数据校验失败')
                            record_logger.debug('数据校验失败')
                            ret_string = '数据校验失败'
                    else:
                        prod_logger.error('发送数据有误')
                        record_logger.debug('发送数据有误')
                        ret_string = '发送数据有误'

                    print(f'[file:{__file__}]=>[line:{
                        (sys._getframe().f_lineno)}]==>{ret_string}')

                    self.masterUI.can_readMsg_thread_close()
                    return ret_string

            def _checkRecvMsg():

                # 读取预期的返回数据11XXXXXXXXXX
                hopeResult = self.testDatas[key]['rcev']
                record_logger.debug(f'预期返回的信号是：{hopeResult}')

                # FIXME: 根据实际情况设定等待信息反馈的时间,当前的线程和接收帧数据的线程不是同一个，
                # 所以当前线程休眠时，接收数据的线程继续工作，如果sleep时间过长，就会导致队列中数据太多
                # 如果sleep时间太短，队列中又可能没有收到数据
                time.sleep(1)
                # 此处把队列中的数据全部取出来，前面发送之前已经清空过一次，这时候取出来就是这个时段收到的数据
                recvMsgs = list(self.recvMsgQueue.deque)
                msgCount = len(recvMsgs)
                record_logger.debug(f'发送信号后，回收到{msgCount}条符合id滤波的数据')
                if msgCount < 1:
                    record_logger.debug('未收到id符合的数据')
                    return False

                # 只有收到数据才会做for循环遍历
                for msg in recvMsgs:
                    # print(msg)
                    if self.rcevId != msg.can_id:
                        record_logger.debug(str(msg) + '----id不匹配')
                        continue
                    else:

                        # 将msg转化为结构化的CanMsg_std_type
                        # ic_debugger(msg_data)
                        msg_data: CanMsg_std_type = self.masterUI.CANMsg2View_std(
                            msg)
                        msg_data_str = msg_data.data_str
                        dataLength = len(hopeResult)
                        for i in range(dataLength):
                            # 设置值不是X，同时也不相等，就跳出去继续下一个
                            if hopeResult[i] != 'X' and hopeResult[i] != msg_data_str[i]:
                                # TODO: 此处的i值是不能马上显示的,最后才统一显示,所以实际上最后只显示了最后一条i值
                                record_logger.debug(
                                    str(msg.data) + f'<<第{i}条msg--不匹配--hope>>' + str(hopeResult))
                                break
                        # 全部都能匹配上，返回True
                        else:
                            record_logger.debug(
                                str(msg.data) + f'<<msg--匹配--hope>>' + str(hopeResult))
                            return True

                # 全部都遍历完了，还没有符合条件的，返回False
                else:
                    record_logger.debug('未回收到预期的反馈信号')
                    return False

            # 因为在外面用result来获取返回值，所以此处不需要对异常进行add_done_callback进行捕获了
            return self.testSinglePoolUnitKey.submit(_sendMsg)
        return wrapper

    def allTest(self):
        """
        左前座椅(主驾驶)
        此处用多线程,避免一个报错,把其他的都给堵塞死了
        """

        dev_logger.debug("run callback allTest....")

        def _allTest():
            dev_logger.debug(f"run _allTest....{self.testDatas}")

            # sendControlMsg 接收测试信息索引号为参数
            res = collections.defaultdict(int)
            for key, _ in enumerate(self.testDatas):
                td = self.sendControlMsg(key)()
                # td 是一个异步线程池任务的future对象，td.result()就是其任务执行后反馈的结果
                # 可能反馈的结果就是在闭包包裹的_sendMsg函数中return的内容,有两种情况，数据校验正确 或 数据校验失败
                # res 是一个字典，key是td.sendControlMsg的返回值,value是计数值
                tdRet = td.result()
                # dev_logger.debug(str(tdRet))
                res[tdRet] += 1
            # res是个字典，示例如下：{"数据校验正确":5, "数据校验错误":3}
            dev_logger.debug(res)
            resString = ''
            # 把测试结果用字典形式列出来
            resFlag = True
            for key, value in res.items():
                # res 是一个字典，key是td.sendControlMsg的返回值,value是计数值
                resString += f'{key}:{value}\n'
                # 只要有一个错误，结果就是错误的，需要确认
                if key != '数据校验正确':
                    resFlag = False
            # 去掉最后的一个空行
            resString = resString[:-1]
            self.alltestResMsg.configure(text=resString)
            foreground = 'green' if resFlag else 'red'
            self.alltestResMsg.configure(foreground=foreground)

            dev_logger.debug('alltest 运行结束....')
            # [ ]:后续考虑优化返回值的结构

            result = AlltestReturnType(resFlag, resString)

            return result

        # 把发送和接受的工作放到一个单独的线程里执行,避免堵塞主线程,每个的发送和接收仍然是同步的,避免self.ecan的使用冲突
        # td = threading.Thread(target=_allTest)
        # td.start()
        # 此处还是要把res return出去，否则在unittestCommand("xy")时，不能正常运行
        res = self.testSinglePoolUnit.submit(_allTest)
        res.add_done_callback(executor_pool_exception_callback)
        return res

        # 把结果返回出去，在外面就可以通过result()接收到，内部函数return的返回值
        # future = self.testSinglePoolUnit.submit(_allTest)
        # 虽然通过future.result()获取到内部函数的返回值，如果内部有报警，会抛出来
        # 此处不能用future.result()，因为result方法是阻塞的，会导致后面无法再继续执行了。
        # future.result()
        # return future
